【初心者向け】VPCエンドポイントとAWS PrivateLinkの違いを実際に構築して理解してみた
もう2度とググりたくない
こんにちは!AWS事業本部のおつまみです。
皆さん「VPCエンドポイントとAWS PrivateLinkの違い」を自分の言葉で説明できますか?私は時折、記憶喪失になり違いを忘れてしまいます。
もう2度とググりたくないという思いがあり、今回は違いをまとめました。
実際にハンズオンできるよう初心者向けの内容となっているため、VPCエンドポイントやPrivateLinkの知識が全くない方のお役に立てればと思います!
結論
- VPCエンドポイント
- VPCと他サービス間でプライベートな接続を提供するコンポーネント
- サービス利用側のVPC内で作成
- AWS PrivateLink
- プライベート接続を介したサービスを提供するためのサービス
- 以下の2つがセットとなり、AWS PrivateLinkが提供されている。
- VPCエンドポイント(サービス利用側のVPC内で作成)
- VPCエンドポイントサービス(サービス提供側のVPC内で作成)
すみません。文章だけはわかりにくいですね。
簡単に図で表しました。
ざっくりAWS PrivateLinkの接続にVPCエンドポイントが使われていると理解できればOKです。
もう少し深く掘り下げてみます。
VPCエンドポイントとは
VPCと他サービス間でプライベートな接続を提供するコンポーネントです。
サービス利用側のVPC内に作成されます。
2022/9/29時点では、これら3種類のVPCエンドポイントが提供されています。
1. インターフェイスエンドポイント
一部のAWSサービスやNLBを介した独自サービス、サポートされているAWS Marketplaceサービスにプライベートに接続する
2. Gateway Load Balancerエンドポイント
Gateway Load Balancerを介したサービスにプライベートに接続する
3. ゲートウェイエンドポイント
S3及びDynamoDBへのアクセスをプライベートに接続する
ポイントは各タイプによって通信経路が異なることです。
1のインターフェイスエンドポイント及び2のGateway Load Balancerエンドポイントの場合は、VPC内にプライベートアドレスをもつENI(Elastic Network Interface)が作成されます。
このENI経由でサービス提供側のVPCエンドポイントサービスに接続されます。
3のゲートウェイエンドポイントの場合は、サービス利用側のルートテーブルにエンドポイントのルーティング設定が必要となります。VPCエンドポイントサービスは作成されません。
詳細な違いは、こちらのブログが大変わかりやすかったです。
次にVPCエンドポイントサービスをみてみましょう。
VPCエンドポイントサービスとは
サービス提供側のVPC内にあるサービスをPrivateLink経由で公開する場合の設定です。
この設定がないと、インターフェイスエンドポイントもしくはGateway Load Balancerエンドポイントにサービスを提供できません。
またゲートウェイエンドポイントの場合はVPCエンドポイントサービスは作成されません。(2回目)
次にAWS PrivateLinkをみてみましょう。
AWS PrivateLinkとは
プライベート接続を介したサービスを提供するためのサービスです。
ネットワーク間のトラフィックをインターネット経由せずに、プライベートに通信することができます。
上記で述べた、以下の2つがセットとなり提供されています。
・VPCエンドポイント(サービス利用側のVPC内で作成)
・VPCエンドポイントサービス(サービス提供側のVPC内で作成)
ちなみにAWS PrivateLinkはAWSのサービス名でありません!
マネジメントコンソールで「AWS PrivateLink」と検索すると、[機能]に以下の2つがヒットします。
これがPrivateLinkの実体ということです。
実際にやってみた
VPCエンドポイントとAWS PrivateLinkの違いは理解できたところで、VPCエンドポイントによるAWS PrivateLink接続を試したいと思います!
今回の構成図です。
ゴールはサービス利用側(Consumer)のEC2からサービス提供側(Provider)のEC2に接続し、Apacheのテストページが表示されることです。
事前準備
ただ全てマネジメントコンソールで作成すると、かなり骨が折れます。 そのため、この構成を下準備として、CloudFormationで以下の構成を事前に作成しておきます。
Consumer.yml
AWSTemplateFormatVersion: "2010-09-09" Description: "Consumer Template." Parameters: # ------------------------------------------------------------# # Common # ------------------------------------------------------------# Prefix: Type: String Default: "prefix" # ------------------------------------------------------------# # Network # ------------------------------------------------------------# VpcCidr: Type: String Default: "10.0.0.0/16" PrivateSubnetCidr: Type: String Default: "10.0.0.0/24" # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# EC2InstanceName: Type: String Default: "ec2" EC2InstanceAMI: Type: AWS::EC2::Image::Id Default: "ami-078296f82eb463377" # Amazon Linux 2 AMI (HVM) - Kernel 5.10, SSD Volume Type EC2InstanceInstanceType: Type: String Default: "t3.nano" EC2InstanceVolumeType: Type: String Default: "gp2" EC2InstanceVolumeSize: Type: String Default: "8" Resources: # ------------------------------------------------------------# # Network # ------------------------------------------------------------# Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub ${Prefix}-vpc PrivateSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PrivateSubnetCidr VpcId: !Ref Vpc AvailabilityZone: Fn::Select: - "0" - Fn::GetAZs: "" Tags: - Key: Name Value: !Sub ${Prefix}-private-subnet PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${Prefix}-table" PrivateSubnetTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet RouteTableId: !Ref PrivateRouteTable SecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: VpcId: !Ref Vpc GroupName: !Sub "${Prefix}-sg" GroupDescription: "-" Tags: - Key: "Name" Value: !Sub "${Prefix}-sg" # ------------------------------------------------------------# # VPC Endpoint # ------------------------------------------------------------# SsmVpcEndpointSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub ${Prefix}-ssm-vpc-endpoint-sg GroupName: !Sub ${Prefix}-ssm-vpc-endpoint-sg VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 SourceSecurityGroupId: !Ref SecurityGroup Tags: - Key: Name Value: !Sub ${Prefix}-ssm-vpc-endpoint-sg SsmVpcEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm VpcId: !Ref Vpc SubnetIds: - !Ref PrivateSubnet SecurityGroupIds: - !Ref SsmVpcEndpointSecurityGroup SsmMessagesVpcEndpointSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub ${Prefix}-ssmmessages-vpc-endpoint-sg GroupName: !Sub ${Prefix}-ssmmessages-vpc-endpoint-sg VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 SourceSecurityGroupId: !Ref SecurityGroup Tags: - Key: Name Value: !Sub ${Prefix}-ssmmessages-vpc-endpoint-sg SsmMessagesVpcEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages VpcId: !Ref Vpc SubnetIds: - !Ref PrivateSubnet SecurityGroupIds: - !Ref SsmMessagesVpcEndpointSecurityGroup # ------------------------------------------------------------# # Ec2InstanceProfile # ------------------------------------------------------------# Ec2Role: Type: AWS::IAM::Role Properties: RoleName: !Sub ${Prefix}-ec2-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore Ec2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: !Sub ${Prefix}-ec2-instance-profile Roles: - !Ref Ec2Role # ------------------------------------------------------------# # EC2Instance # ------------------------------------------------------------# EC2Instance: Type: "AWS::EC2::Instance" Properties: Tags: - Key: Name Value: !Sub "${Prefix}-${EC2InstanceName}" ImageId: !Ref EC2InstanceAMI InstanceType: !Ref EC2InstanceInstanceType IamInstanceProfile: !Ref Ec2InstanceProfile DisableApiTermination: false EbsOptimized: false BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true VolumeType: !Ref EC2InstanceVolumeType VolumeSize: !Ref EC2InstanceVolumeSize SecurityGroupIds: - !Ref SecurityGroup SubnetId: !Ref PrivateSubnet UserData: !Base64 | #! /bin/bash yum update -y
Provider.yml
AWSTemplateFormatVersion: "2010-09-09" Description: "Provider Template." Parameters: # ------------------------------------------------------------# # Common # ------------------------------------------------------------# Prefix: Type: String Default: "prefix" # ------------------------------------------------------------# # Network # ------------------------------------------------------------# VpcCidr: Type: String Default: "10.1.0.0/16" PrivateSubnetCidr: Type: String Default: "10.1.0.0/24" # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# EC2InstanceName: Type: String Default: "ec2" EC2InstanceAMI: Type: AWS::EC2::Image::Id Default: "ami-078296f82eb463377" # Amazon Linux 2 AMI (HVM) - Kernel 5.10, SSD Volume Type EC2InstanceInstanceType: Type: String Default: "t3.nano" EC2InstanceVolumeType: Type: String Default: "gp2" EC2InstanceVolumeSize: Type: String Default: "8" Resources: # ------------------------------------------------------------# # Network # ------------------------------------------------------------# Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub ${Prefix}-vpc PrivateSubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: !Ref PrivateSubnetCidr VpcId: !Ref Vpc AvailabilityZone: Fn::Select: - "0" - Fn::GetAZs: "" Tags: - Key: Name Value: !Sub ${Prefix}-private-subnet PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${Prefix}-table" PrivateSubnetTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet RouteTableId: !Ref PrivateRouteTable SecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: "0.0.0.0/0" GroupName: !Sub "${Prefix}-sg" GroupDescription: "-" Tags: - Key: "Name" Value: !Sub "${Prefix}-sg" # ------------------------------------------------------------# # VPC Endpoint # ------------------------------------------------------------# VPCS3Endpoint: Type: "AWS::EC2::VPCEndpoint" Properties: RouteTableIds: - !Ref PrivateRouteTable ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3" VpcEndpointType: Gateway VpcId: !Ref Vpc # ------------------------------------------------------------# # EC2Instance # ------------------------------------------------------------# EC2Instance: Type: "AWS::EC2::Instance" Properties: Tags: - Key: Name Value: !Sub "${Prefix}-${EC2InstanceName}" ImageId: !Ref EC2InstanceAMI InstanceType: !Ref EC2InstanceInstanceType DisableApiTermination: false EbsOptimized: false BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true VolumeType: !Ref EC2InstanceVolumeType VolumeSize: !Ref EC2InstanceVolumeSize SecurityGroupIds: - !Ref SecurityGroup SubnetId: !Ref PrivateSubnet UserData: !Base64 | #! /bin/bash sudo yum update -y sudo yum -y install httpd sudo systemctl start httpd sudo systemctl enable httpd # ------------------------------------------------------------# # NLB # ------------------------------------------------------------# TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: VpcId: !Ref Vpc Name: !Sub "${Prefix}-tg" Protocol: TCP Port: 80 Tags: - Key: Name Value: !Sub "${Prefix}-tg" Targets: - Id: !Ref EC2Instance Port: 80 NLB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub "${Prefix}-nlb" Tags: - Key: Name Value: !Sub "${Prefix}-nlb" Scheme: "internal" LoadBalancerAttributes: - Key: "deletion_protection.enabled" Value: false Subnets: - !Ref PrivateSubnet Type: network NLBListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref NLB Port: 80 Protocol: TCP
CloudFormationテンプレートはこちらの記事で公開されているものをカスタマイズさせていただきました。 ありがとうございます!!
いよいよAWS PrivateLink接続のための手順となります。今回は吹き出しの部分をマネジメントコンソールを触りながら、理解を深めたいと思います。
1. VPCエンドポイントサービスの作成
VPCサービスより「エンドポイントサービス」を選択し、「エンドポイントサービスの作成」をクリックします。
以下のように任意の名前を入力し、「ロードバランサーのタイプ」を「ネットワーク」にし、ロードバランサーにはCloudFormationで作成されたNLBを指定します。
「承諾が必要」にチェックを入れておきます。
ここにチェックを入れることで、第3者からの接続を防ぐことができます。
チェック後、「作成」をクリックします。
Consumer側でエンドポイント作成時に使用する「サービス名」を控えておきます。
2. VPCエンドポイントの作成
VPCサービスより「エンドポイント」を選択します。
既にCloudFormationで作成された S3のゲートウェイエンドポイントとSession Managerを使用するためのエンドポイントが作成されています。「エンドポイントの作成」をクリックします。
以下のように任意の名前を入力し、「サービスカテゴリ」に「その他のエンドポイントサービス」を指定します。「サービス名」には先程控えておいたサービス名を指定します。入力後、「サービスの検証」をクリックします。
「サービス名が検証されました」が出力されればOKです。
VPCやサブネットはConsumer側のEC2が配置されているものを指定します。
NLBのエンドポイント用のセキュリティグループを作成していなかったため、今回はdefaultを指定しました。
※エンドポイント用のセキュリティグループを作成することを推奨します。
後ほど、 Consumer側のEC2インスタンスから接続できるようにセキュリティグループを修正します。
セキュリティグループ指定後、「エンドポイントを作成」をクリックします。
作成したエンドポイントの「ステータス」が「pendingAcceptance」になりました。
話は逸れますが、この画面からインターフェイスエンドポイントのみENIが作成され、ゲートウェイエンドポイントにはENIが作成されてないことがわかります。
先にVPCエンドポイントのセキュリティグループを修正しておきます。 エンドポイントのセキュリティグループよりグループIDを指定します。
セキュリティグループのインバウンドルールにCunsumer側のEC2インスタンスに設定されているプライベートIPアドレスからのHTTP通信を許可するように設定します。
3. エンドポイントの承諾
再度VPCサービスより「エンドポイントサービス」を選択し、作成されているエンドポイントサービスの「エンドポイント接続」のタブをクリックします。
状態が「pendingAcceptance」中のエンドポイントが1つ紐づいていることがわかります。
「アクション」から「エンドポイント接続リクエストの承諾」を行います。
承諾確認画面が表示されるため、「承諾」を入力し、「承諾」をクリックします。
しばらくすると、作成したエンドポイントの「ステータス」が「使用可能」になります。
作成された「エンドポイント」のDNS名より、NLBを経由してProvider側のEC2にアクセスできるようになります。
ちなみにDNS名には、「リージョン固有のDNSホスト名」と「ゾーンごとのDNSホスト名」の2つが発行されています。どちらからでも接続できますが、原則的にはリージョンDNS名を利用し、AZ跨ぎのレイテンシや通信料金が気になる場合のみゾーン固有のDNSを利用するのがよいようです。
4. サービス利用側のEC2から接続確認
最後にConsumer側のEC2インスタンスからの接続を確認します。 EC2サービスからConsumer側のEC2インスタンスを選択し、「接続」をクリックします。
「セッションマネージャー」のタブから「接続」をクリックします。
先程控えておいた「エンドポイント」のDNS名を指定して、アクセスしてみます。 無事、Apacheのテストページが表示されることが確認できました!
最後に
今回はVPCエンドポイントとAWS PrivateLinkの違いを実際に構築して理解してみました。
恥ずかしながら、今までインターフェースエンドポイントとAWS PrivateLinkは同義だと思い込んでいました。。。
実際に構築したことで違いを知るきっかけになったので、やはり実践が1番ですね!
おそらくもう2度とググることはないと思います。
今回は触れませんでしが、AWS PrivateLink・VPCエンドポイントを利用するメリットやユースケースはこちらの記事がわかりやすかったので参考にしてください。
最後までお読みいただきありがとうございました! この記事が誰かのお役に立てば幸いです。
以上、おつまみ(@AWS11077)でした!
参考
AWS PrivateLink および VPC エンドポイント - Amazon Virtual Private Cloud